package com.example.lockspringbootstarter.lock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Description :
 * @Author : YTY
 * @Date : 2024-05-23 08:22
 */
public class RedisLock extends AbstractLock {

    private static final Logger LOG = LoggerFactory.getLogger(RedisLock.class);
    private StringRedisTemplate stringRedisTemplate;
    private final long defaultExpireTime = 30 * 1000;
    private final String uuid;
    private Long expireTime = 0l;

    public RedisLock(StringRedisTemplate stringRedisTemplate, String lockName) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.lockName = lockName;
        this.uuid = UUID.randomUUID().toString();
    }

    @Override
    public void lock() {
        lock(TimeUnit.MILLISECONDS, defaultExpireTime);
    }

    @Override
    public void lock(TimeUnit timeUnit, Long expireTime) {
        this.expireTime = expireTime;
        while (true) {
            // 1、使用setnx指令加锁
            // Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(this.lockName, uuid, expireTime, timeUnit);
            // 使用lua脚本加锁
            // String luaScript = "if(redis.call('exists', KEYS[1]) == 0) then redis.call('set', KEYS[1], ARGV[1]) redis.call('pexpire', KEYS[1], ARGV[2]) return 1; else return 0;  end";
            // 使用lua脚本加锁【可重入锁】
            String luaScript = "if(redis.call('exists', KEYS[1]) == 0) then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('pexpire', KEYS[1], ARGV[2]) return 1; end if (redis.call('hexists',KEYS[1], ARGV[1]) == 1) then redis.call('hincrby', KEYS[1], ARGV[1], 1) redis.call('pexpire', KEYS[1], ARGV[2]) return 1; else return 0; end";
            Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class)
                    , Collections.singletonList(this.lockName),
                    uuid,
                    expireTime.toString());
            LOG.info("我没获取到锁，自旋等待{}", result);
            if (result != null && result.equals(1L)) {
                new Thread(() -> {
                    while (true) {
                        String expireLua = "if(redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0; else redis.call('pexpire', KEYS[1], ARGV[2]) return 1; end";
                        Long expireResult = stringRedisTemplate.execute(new DefaultRedisScript<>(expireLua, Long.class)
                                , Collections.singletonList(this.lockName),
                                uuid,
                                expireTime.toString());
                        if (expireResult == null || expireResult.equals(0L)) {
                            break;
                        }
                        try {
                            Thread.sleep(expireTime / 2);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }).start();
                break;
            }

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void unlock() {
        // 判断：当前持有锁的线程，是否等于本线程，只有是才删除锁
        // String currentLockName = stringRedisTemplate.opsForValue().get(this.lockName);
        // if (this.uuid.equals(currentLockName)) {
        //     stringRedisTemplate.delete(this.lockName);
        // }

        //释放锁lua脚本
        // String luaScript = "if(redis.call('exists', KEYS[1])== 0) then return 0; end if (redis.call('get', KEYS[1]) == ARGV[1]) then redis.call('del', KEYS[1]) return 1; else return 0; end";
        // stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class)
        //         , Collections.singletonList(this.lockName), uuid);

        //释放锁lua脚本【可重入锁】
        String luaScript = "if(redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return 0; end local lockCount = redis.call('hincrby', KEYS[1], ARGV[1], -1) if(lockCount > 0) then redis.call('pexpire', KEYS[1], ARGV[2]) return 1; else redis.call('del', KEYS[1]) return 1; end";
        stringRedisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class)
                , Collections.singletonList(this.lockName), uuid, expireTime.toString());
    }
}
